package net.aiotos.driver;

import IceUtilInternal.StringUtil;
import com.alibaba.fastjson.JSONArray;
import net.aiotos.common.dto.*;
import lombok.NonNull;
import lombok.extern.slf4j.Slf4j;
import net.aiotos.common.exception.AccountNotRegisterError;
import net.aiotos.common.exception.RequestNotAuthError;
import net.aiotos.common.exception.SelfOfflineError;

import java.io.File;
import java.lang.reflect.Constructor;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.*;

//开启多线程
@Slf4j
public class RunCollectionThread extends Thread {
    private final IOTOSys zm;
    public volatile boolean exit;
    private final Object lock;
    private final String devId;
    private final String deviceOid;
    private final Map<String, String> proxyIndex2DataId = new HashMap<>();
    private IOTOSDriverI driverInit;
    public RunCollectionThread(IOTOSys zm, Object lock, String devId) throws ClassNotFoundException, Exception {
        super();
        this.zm = zm;
        this.devId = devId;
        this.lock = lock;
        deviceOid = StringUtil.splitString(devId, ".")[1];

        this.setName(String.format("%s-%s-%s", zm.iotoSysI.getIonodeDTO().getProperties().get(deviceOid).getName(), "CollectionDriver", StringUtil.splitString(devId, ".")[1]));
        DeviceDTO deviceDTO = zm.mdevAttrs.get(devId);
        DeviceConfigDTO devCfg = deviceDTO.getConfig(DeviceConfigDTO.class);
        String className = null;
        try {
            //实例化设备驱动
            if (devCfg.getDriver() != null || devCfg.getJava() != null ) {
                String[] nameGroupTmp;
                if(devCfg.getJava() != null && !devCfg.getJava().isEmpty()) {
                    nameGroupTmp = devCfg.getJava().split("\\/");
                } else {
                    nameGroupTmp = devCfg.getDriver().split("\\/");
                }

                //获取语言
                String importcfg = nameGroupTmp[0];

                if ("java".equals(importcfg)) {
                    className = nameGroupTmp[1];

                    loadByNewClassLoader(className, deviceDTO);

                    //实例化驱动对象
                    driverInit.setZm(this.zm);
                    log.info("驱动启动成功：{}->{}", driverInit.getClass().getName(), driverInit.getSysId());
                    zm.getDevList();
                    HashMap<String, String[]> data2Subs = new HashMap<>();
                    List<String> mdevpoints = zm.mdevpoints.get(String.format("%s.%s", zm.getUuid(), deviceDTO.getDeviceOid()));
                    for (String pointId : mdevpoints) {
                        String[] strings = StringUtil.splitString(pointId, ".");
                        String dataIdtmp = strings[2];
                        DataDTO attrtmp = zm.mpointAttrs.get(pointId);
                        data2Subs.put(dataIdtmp, zm.mpoint2subs.get(pointId).toArray(new String[]{}));
                    }
                    driverInit.setData2subs(data2Subs);
                    this.zm.mdev2driver.putIfAbsent(devId, driverInit);
                }

            }
        } catch (ClassNotFoundException ex) {
            log.error("驱动类:{}，不存在。请用检查包名或类名是否正确。", className);
            this.exit = true;
            throw ex;
        } catch (Exception ex) {
            log.error("驱动启动异常:{}", className);
            throw ex;
        }
    }

    public void loadByNewClassLoader(@NonNull String className, @NonNull DeviceDTO deviceDTO) throws Exception {
        //通过反射找到类里面的方法和属性
        log.info("驱动类实例化：{}", className);
        Class<IOTOSDriverI> driver;
        if(className.indexOf(":") == -1) {
            driver = (Class<IOTOSDriverI>) Class.forName(className);
        } else {
            String jarName;
            String jarPath;
            String className2;

            String[] strings = className.split(":");
            jarName = strings[0];
            className2 = strings[1];
            jarPath = String.format("%s%s%s%s%s", zm.runDir, File.separator, "lib", File.separator, jarName);
            log.info("jarPath:{}", jarPath);
            log.info("className:{}", className2);
            File file = new File(jarPath);
            if(!(file.exists() && file.isFile() && file.canRead())) {
                throw new ClassNotFoundException(className);
            }
            URL[] urls = new URL[]{file.toURI().toURL()};
            URLClassLoader urlClassLoader = new URLClassLoader(urls, this.getContextClassLoader());
            driver = (Class<IOTOSDriverI>) urlClassLoader.loadClass(className2);
        }
        Constructor<IOTOSDriverI> driverConstructor = driver.getConstructor(DeviceDTO.class);
        driverInit = driverConstructor.newInstance(deviceDTO);
    }

    //启动线程
    @SuppressWarnings("static-access")
    public void run() {
        try {
            synchronized (lock) {
                zm.eventcount++;
                IOTOSDriverI drtmp = null;
                if (zm.mdev2driver.containsKey(devId)) {
                    exit = true;
                    drtmp = zm.driver(devId);
                    drtmp.InitComm();
                }
                while (exit) {
                    Map<String, List<String>> mpointAttrs = zm.mdevpoints;
                    List<String> dataIdList = mpointAttrs.get(devId);
                    for (String pointId : dataIdList) {
                        if (zm.restartCollect) {
                            break;
                        }
                        if (zm.pauseCollect) {
                            //线程等待
                            wait();
                            lock.notifyAll();
                        }

                        String dataId = pointId.split("\\.")[2];
                        DataDTO dataDTO = drtmp.getDataDTO(dataId);
                        DataConfigDTO dataConfig = dataDTO.getConfig(DataConfigDTO.class);
                        // 循环采集间隔
                        int refreshcycle = dataDTO.getRefreshcycle();

                        if( refreshcycle > 0 ) {
                            //log.info("----------\t according to config, will sleep for:{}", refreshcycle);
                            Thread.sleep(refreshcycle);
                        }

                        // 采集禁用识别
                        if(dataConfig.getDisabled() != null && dataConfig.getDisabled()) {
                            log.warn("data disabled: {}", dataId);
                            continue;
                        }

                        Object[] valtmps = drtmp.Collectig(dataDTO);
                        List<DataPubDTO> dataPubDTOS = new ArrayList<>();
                        DataPubDTO currentData = null;
                        if(valtmps == null || valtmps.length == 0) {
                            continue;
                        } else {
                            currentData = driverInit.dataPub(dataId, valtmps[0]);
                            dataPubDTOS.add(currentData);
                        }

                        if(valtmps.length == 1) {
                            ResultDTO ret = null;
                            try {
                                ret = drtmp.setValue(dataDTO.getName(), currentData.getValue().toString());
                                ret.check();
                                // 成功不输出日志
                                // log.info("data={}, type={}, value={}, ret={}", dataId, dataDTO.getValuetype(), JSONArray.toJSON(valtmp).toString(), ret);
                            } catch (SelfOfflineError ex) {
                                // 自己已经掉线，需要重新上线
                                log.error("data={}, type={}, value={}, ret={}", dataId, dataDTO.getValuetype(), JSONArray
                                        .toJSON(valtmps)
                                        .toString(), ret);

                                LoginResp loginResp = zm.loginIce();
                                log.error("自己已经掉线", ex);
                                log.warn("重新上线结果:{}", loginResp.toString());

                            } catch ( AccountNotRegisterError  ex) {
                                log.warn("帐号未注册:{}", ex);
                                LoginResp loginResp = zm.iotoSysI.loginWeb();
                                if(loginResp.getCode() == 0) {
                                    log.info("帐号未注册，重新登录:{}", loginResp);
                                } else {
                                    log.info("AccountNotRegisterError", ex);
                                    System.exit(-1);
                                }
                            } catch ( RequestNotAuthError  ex) {
                                log.warn("授权失效:{}", ex);
                                LoginResp loginResp = zm.iotoSysI.loginWeb();
                                if(loginResp.getCode() == 0) {
                                    log.info("授权失效，重新登录:{}", loginResp);
                                } else {
                                    log.info("RequestNotAuthError", ex);
                                    System.exit(-1);
                               }
                            } catch (Ice.ConnectionLostException ex) {
                                // 不需要自己处理，ICE有相应处理机制，等待便可
                                log.error("此异常表示连接丢失：data={}, value={}", dataId, valtmps, ex);
                            } catch (Ice.ConnectTimeoutException ex) {
                                // 不需要自己处理，ICE有相应处理机制，等待便可
                                log.error("连接网关超时：data={}, value={}", dataId, valtmps, ex);
                            } catch (Exception ex) {
                                log.error("data={}, value={}", dataId, valtmps, ex);
                            }
                        } else {
                            log.info("pub mutiple data...");
                            List<DataPubDTO> dataList = new ArrayList<>();
                            // 要批量上报的值结构，其中返回的值元组中第一个就是采集点自身的值，所以先append走一个！
                            dataList.add(currentData);
                            HashMap<Short, String> proxyMap = new HashMap<Short, String>();
                            for (Map.Entry<String, DataDTO> entry : drtmp.getData2attrs().entrySet()) {
                                String idtmp = entry.getKey();
                                DataDTO attrtmp = entry.getValue();
                                DataConfigDTO dataCfg = attrtmp.getConfig(DataConfigDTO.class);
                                String proxyedId = dataCfg.getProxy().getPointer();
                                Short index = dataCfg.getProxy().getIndex();
                                log.info("proxyedId:{}->{}=>{}", dataId, proxyedId, index);
                                if(proxyedId == null) {
                                    continue;
                                }
                                proxyMap.put(index, entry.getKey());
                            }

                            for (Short i = 1; i < valtmps.length; i++) {
                                if(!proxyMap.containsKey(i)){
                                    continue;
                                }
                                String tmpDataId = proxyMap.get(i);
                                dataList.add(driverInit.dataPub(tmpDataId, valtmps[i]));
                            }
                            List<ResultDTO> retJson = drtmp.setValues(dataList);
                            log.info("setValues:{}", retJson);
                        }
                    }

                    if(drtmp.isCollectingOneCircle()) {
                        log.warn("单次遍历完毕，采集循环退出！");
                        break;
                    }
                }
            }
        } catch (RequestNotAuthError ex) { // 收集请求授权失效，然后重新登录
            log.error("请求授权失效，正在尝试重新登录", ex);
        } catch (Exception ex) {
            // TODO: handle exception
            log.error("驱动采集异常", ex);
        }

    }



    //停止线程
    @SuppressWarnings("static-access")
    public void stops() {
        zm.eventcount--;
        if (zm.eventcount < 0) {
            zm.eventcount = 0;
        }
        exit = false;
    }
}
